/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * 知启蒙Servlet兼容库（zhiqim_servlet）在LGPL3.0协议下开源：https://www.zhiqim.com/gitcan/zhiqim/zhiqim_servlet.htm
 *
 * This file is part of [zhiqim_servlet].
 * 
 * [zhiqim_servlet] is free software: you can redistribute
 * it and/or modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * [zhiqim_servlet] is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with [zhiqim_servlet].
 * If not, see <http://www.gnu.org/licenses/>.
 */
package org.zhiqim.servlet;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.zhiqim.httpd.HttpCookie;
import org.zhiqim.httpd.HttpResponse;
import org.zhiqim.httpd.HttpdConstants;
import org.zhiqim.httpd.constants.HttpStatus;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Validates;

/**
 * ZML响应封装类，封装HttpServletResponse实现HttpResponse
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class ZmlResponse implements HttpResponse, HttpdConstants
{
    private HttpServletResponse response;
    
    private ZmlRequest request;
    private boolean isCommited;
    
    public ZmlResponse(HttpServletResponse response)
    {
        this.response = response;
    }
    
    @Override
    public String getId()
    {
        return request.getId();
    }
    
    /***********************************************************************/
    // 关联连接信息
    /***********************************************************************/
    
    /** 设置请求 */
    void setRequest(ZmlRequest request)
    {
        this.request = request;
    }
    
    /** 获取当前请求 */
    public ZmlRequest getRequest()
    {
        return request;
    }
    
    /** 获取上下文环境 */
    public ZmlContext getContext()
    {
        return request.getContext();
    }
    
    /***********************************************************************/
    // 状态处理和判断
    /***********************************************************************/
    
    /** 是否已提交 */
    public boolean isCommitted()
    {
        return isCommited || response.isCommitted();
    }
    
    /** 是否可编辑 */
    public boolean isEditable()
    {
        return !isCommitted();
    }
    
    /** 提交 */
    public void commit()
    {
        isCommited = true;
    }
    
    /***********************************************************************/
    // 设置状态或，或重定向，或错误重定向
    /***********************************************************************/
    
    /**
     * 发生错误，返回错误码和标准的错误信息
     * 
     * @param code          错误码
     * @throws IOException  可能的异常
     */
    public void sendError(int code) throws IOException
    {
        setStatus(code);
        print(HttpStatus.getStatusMsg(code));
        commit();
    }
    
    /**
     * 发生错误，返回错误状态和一句错误描述，如果描述为null，则取标准错误描述
     * 
     * @param code              错误码
     * @param message           错误信息
     * @throws IOException      可能的异常
     */
    public void sendError(int code, String message) throws IOException
    {        
        setStatus(code);
        print(message);
        commit();
    }
    
    @Override
    public void sendErrorHTML(int code) throws IOException
    {
        sendErrorHTML(code, null);
    }

    @Override
    @SuppressWarnings("deprecation")
    public void sendErrorHTML(int code, String reason) throws IOException
    {
        if (Validates.isEmptyBlank(reason))
            reason = HttpStatus.getStatusMsg(code);
        
        //content
        StringBuilder strb = new StringBuilder();
        strb.append(_HTML_5_TYPE_).append(_BR_);
        strb.append(_HTML).append(_BR_);
        strb.append(_HEAD).append("<title>Error ").append(code).append("</title>").append(_HEAD).append(_BR_);
        strb.append(_BODY).append("<h2>Error ").append(reason).append("</h2>").append(BODY_).append(_BR_);
        strb.append(HTML_).append(_BR_);
        
        response.getOutputStream().write(strb.toString().getBytes());
        response.setStatus(code, reason);
    }

    @Override
    public void sendContent(int code, String content) throws IOException
    {
        response.getOutputStream().write(content.getBytes());
        response.flushBuffer();
    }
    
    /**
     * 只返回消息头，如304等，如果在前面已print内容，也会写入到内容，如果没有在前面写入内容，则不会写内容
     * 
     * @param code              响应码，可以是200表示成功
     * @throws IOException      可能的异常
     */
    public void sendHeader(int code) throws IOException
    {
        setStatus(code);
        commit();
    }
    
    /**
     * 客户端重定向，将资源指身一个永久URI，采用302把URI写在location消息头中
     * 
     * @param location          重定向连接地址
     * @throws IOException      可能的异常
     */
    public void sendRedirect(String location) throws IOException
    {
        if (isCommitted())
            throw new IllegalStateException("Commited");
        
        response.sendRedirect(location);
        commit();
    }
    
    /**
     * 一般用于返回200-207的成功码和提示的一个内容，和sendError不同的是sendError的reason=content，而sendSuccess的reason=默认值
     *  
     * @param code              成功码
     * @param content           内容
     * @throws IOException      可能的异常
     */
    public void sendSuccess(int code, String content) throws IOException
    {
        setStatus(code);
        print(content);
        commit();
    }
    
    /** 编码URL，增加非useCookie时有可能的jsessionid，在URL跳转时需要 */
    public String encodeURL(String url)
    {
        return response.encodeURL(url);
    }
    
    /** 获取响应状态码 */
    public int getStatus()
    {
        return response.getStatus();
    }
    
    /** 获取状态描述 */
    public String getReason()
    {
        return HttpStatus.getStatusMsg(response.getStatus());
    } 
    
    /** 设置响应状态码 */
    public void setStatus(int code)
    {
        response.setStatus(code);
    }
    
    /***********************************************************************/
    // 设置头部信息
    /***********************************************************************/
    
    /** 设置头部域 */
    public void setHeader(String key, Object value)
    {
        if (value == null)
            return;
        
        response.setHeader(key, String.valueOf(value));
    }
    
    /** 设置头部日期格式域 */
    public void setDateHeader(String key, long value)
    {
        response.setDateHeader(key, value);
    }
    
    /** 设置缓存控制域 */
    public void setCacheControl(String value)
    {
        setHeader(_CACHE_CONTROL_, value);
    }
    
    /** 设置缓存控制域为私有 */
    public void setCacheControlPrivate()
    {
        setHeader(_CACHE_CONTROL_, "private");
    }
    
    /** 设置头部域，增加一个属性 */
    public void addHeader(String key, Object value)
    {
        if (value == null)
            return;
        
        response.addHeader(key, String.valueOf(value));
    }
    
    /** 设置头部日期格式域，增加一个日期类型的属性 */
    public void addDateHeader(String key, long value)
    {
        response.addDateHeader(key, value);
    }
    
    /** 删除头部域 */
    public void removeHeader(String key)
    {
        if (!hasHeader(key))
            return;
        
        for (Iterator<String> it=response.getHeaderNames().iterator();it.hasNext();)
        {
            if (key.equals(it.next()))
                it.remove();
        }
    }
    
    /** 获取头部域 */
    public String getHeader(String key)
    {
        return response.getHeader(key);
    }
    
    /** 判断是否有头部域 */
    public boolean hasHeader(String key)
    {
        return response.getHeaders(key) != null;
    }
    
    /** 设置编码格式 */
    public void setCharacterEncoding(String characterEncoding)
    {
        Asserts.as(Validates.isNotEmptyBlank(characterEncoding)?null:"不支持传入空的编码格式");
        this.response.setCharacterEncoding(characterEncoding);
    }
    
    /** 获取编码格式 */
    public String getCharacterEncoding()
    {
        return response.getCharacterEncoding();
    }
    
    /** 设置头部域中内容类型 */
    public void setContentType(String contentType)
    {
        Asserts.as(Validates.isNotEmptyBlank(contentType)?null:"不支持传入空的内容类型");
        response.setContentType(contentType);
    }
    
    /**
     * 设置内容类型，不设置字符集到消息头中
     * 
     * @param contentType       内容类型
     */
    public void setContentTypeNoCharset(String contentType)
    {
        Asserts.as(Validates.isNotEmptyBlank(contentType)?null:"不支持传入空的内容类型");
        response.setContentType(contentType);
    }
    
    /** 设置头部域中内容长度 */
    public void setContentLength(int contentLength)
    {
        response.setContentLength(contentLength);
    }
    
    /** 获取内容长度 */
    public int getContentLength()
    {
        String value = getHeader(_CONTENT_LENGTH_);
        return (value == null || !Validates.isIntegerNonnegative(value))?0:Ints.toInt(value);
    }
    
    /***********************************************************************/
    // 设置Cookie和是否处理session
    /***********************************************************************/
    
    /**
     * 增加cookie值
     * 
     * @param cookieName    名称
     * @param cookieValue   值
     */
    @Override
    public void addCookie(String cookieName, String cookieValue)
    {
        addCookie(cookieName, cookieValue, 0);
    }
    
    /**
     * 增加cookie值
     * 
     * @param cookieName    名称
     * @param cookieValue   值
     * @param seconds       存活时间，单位：秒
     */
    @Override
    public void addCookie(String cookieName, String cookieValue, int seconds)
    {
        Cookie cookie = new Cookie(cookieName, cookieValue);
        cookie.setMaxAge(seconds);
        cookie.setHttpOnly(true);
        cookie.setPath(request.getContextPath());
        
        response.addCookie(cookie);
    }
    
    @Override/** 增加Cookie值 */
    public void addCookie(HttpCookie cookie)
    {
        Cookie c = new Cookie(cookie.getName(), cookie.getValue());
        c.setHttpOnly(true);
        c.setPath(request.getContextPath());
        if (cookie.getMaxAge() > 0)
            c.setMaxAge(cookie.getMaxAge());
        
        response.addCookie(c);
    }
    
    /** 删除Cookie值 */
    public void removeCookie(String name)
    {
        addCookie(name, null, -1);
    }
    
    @Override/** 清空Cookie */
    public void clearCookie()
    {
        throw Asserts.exception("该方法禁止在Servlet容器中调用，因为Servlet未提供接口");
    }
    
    /***********************************************************************/
    // 数据流处理，包括写入、打印和刷新关闭等
    /***********************************************************************/
    
    /** 获取输出流 */
    public OutputStream getOutputStream()
    {
        try
        {
            return response.getOutputStream();
        }
        catch (IOException e)
        {
            return null;
        }
    }
    
    /** 写内容字节方式 */
    public void write(byte[] b) throws IOException
    {
        if (response.getContentType() == null)
            response.setContentType("text/html");
        
        response.getOutputStream().write(b);
    }
    
    /** 写内容,无回车换行 */
    public void print(String str) throws IOException
    {
        if (getCharacterEncoding() == null)
            setCharacterEncoding(_UTF_8_);
        
        write(str.getBytes(getCharacterEncoding()));
    }
    
    /** 写回车换行 */
    public void println() throws IOException
    {
        write(_CRLF_);
    }
    
    /** 写内容加回车换行 */
    public void println(String str) throws IOException
    {
        print(str);
        write(_CRLF_);
    }
    
    /** 刷新流 */
    public void flush() throws IOException
    {
        response.getOutputStream().flush();
    }
    
    /** 关闭流 */
    public void close() throws IOException
    {
        response.getOutputStream().close();
    }
    
    /***********************************************************************/
    // toString
    /***********************************************************************/
    
    public String toString()
    {
        StringBuilder strb = new StringBuilder();
        strb.append(request.getVersion()).append(_SPACE_)
            .append(response.getStatus()).append(_SPACE_)
            .append(HttpStatus.getStatusMsg(response.getStatus())).append(_BR_);
        for (String key : response.getHeaderNames())
        {
            String value = response.getHeader(key);
            strb.append(key).append(_COLON_).append(value).append(_BR_);
        }
        
        strb.append(_BR_);
        return strb.toString();
    }
}
